package com.mtw.bbs.like.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mtw.bbs.common.core.exception.BusinessException;
import com.mtw.bbs.common.core.util.BeanCopyUtils;
import com.mtw.bbs.common.core.util.ExecutorUtilz;
import com.mtw.bbs.common.core.vo.Result;
import com.mtw.bbs.common.redisBase.util.RedisUtilz;
import com.mtw.bbs.common.redisBase.util.TimeUtilz;
import com.mtw.bbs.constant.LikeConstant;
import com.mtw.bbs.constant.LikeOperateEnum;
import com.mtw.bbs.like.job.LikeAsyncJob;
import com.mtw.bbs.like.mapper.LikeRecordMapper;
import com.mtw.bbs.like.pojo.dto.LikeRecordDto;
import com.mtw.bbs.like.pojo.entity.LikeRecord;
import com.mtw.bbs.like.service.LikeRecordService;
import com.mtw.bbs.userAdmin.feign.UserClient;
import com.mtw.bbs.userAdmin.pojo.dto.UserDto;
import com.mtw.bbs.userAdmin.pojo.vo.UserInfoVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.mtw.bbs.common.core.constant.RedisConstant.NAME_SPACE;

/**
 * 点赞逻辑层
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LikeRecordServiceImpl extends ServiceImpl<LikeRecordMapper, LikeRecord> implements LikeRecordService {

    private final UserClient userClient;
    private final LikeAsyncJob likeAsyncJob;
    private final ExecutorUtilz executorUtilz;
    private final RedissonClient redissonClient;
    private final RedisUtilz<String,String> redisUtilz;






    /**
     * 用户是否点赞过
     * @return                      1：已点赞，0,已踩
     */
    @Override
    @Cacheable(cacheNames = "uLiked", key = "#dto.userMid + ':' + #dto.objectType  + ':'+  #dto.objectId")
    public int userIsLiked(LikeRecordDto dto) {

        String redisKey = getUserRedisKey(dto);

        // 无缓存
        if (!redisUtilz.hasKey(redisKey)){
            LambdaQueryWrapper<LikeRecord> lqw = new LambdaQueryWrapper<>();
            lqw.select(LikeRecord::getObjectId,LikeRecord::getModifiedDate,LikeRecord::getLikeStatus);
            lqw.eq(LikeRecord::getLikeStatus,LikeConstant.LIKE_VALUE);
            lqw.eq(LikeRecord::getUserMid,dto.getUserMid());
            lqw.orderByDesc(LikeRecord::getModifiedDate);
            Page<LikeRecord> recordPage = this.page(new Page<>(1, 2000), lqw);

            // 重建缓存
            HashSet<ZSetOperations.TypedTuple<String>> set = new HashSet<>((int) recordPage.getTotal());
            for (LikeRecord x : recordPage.getRecords()) {
                ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<>(x.getObjectId(), (double) x.getLikeStatus());
                set.add(typedTuple);
            }
            if (CollectionUtil.isEmpty(recordPage.getRecords())){
                ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<>("", 0d);
                set.add(typedTuple);
            }
            redisUtilz.batchAddZset(redisKey,set);
            redisUtilz.expire(redisKey,60+ TimeUtilz.getRandomMinutes(), TimeUnit.MINUTES);
        }

        // 读取缓存
        Double score = redisUtilz.score(redisKey, dto.getObjectId());
        if (score != null){
            return  score.intValue();
        }

        // 缓存无数据或为踩，查数据库
        LambdaQueryWrapper<LikeRecord> lqw = new LambdaQueryWrapper<>();
        lqw.select(LikeRecord::getLikeStatus);
        lqw.eq(LikeRecord::getUserMid,dto.getUserMid());
        lqw.eq(LikeRecord::getObjectId,dto.getObjectId());
        lqw.eq(LikeRecord::getObjectType,dto.getObjectType());
        lqw.last(" limit 1");
        List<LikeRecord> list = list(lqw);
        if (list.isEmpty()){
            return LikeConstant.NONE_VALUE;
        }
        if (LikeConstant.DISLIKE_VALUE.equals(list.get(0).getLikeStatus())){
            return LikeConstant.DISLIKE_VALUE;
        }
        redisUtilz.zAdd(redisKey,dto.getObjectId(),(double) list.get(0).getLikeStatus());
        return list.get(0).getLikeStatus();
    }






    /**
     * 分页获取已点赞的人
     * @param page              当前页
     * @param size              分页大小
     */
    @Override
    public List<UserInfoVo> getUserListForLiked(int page, int size, LikeRecordDto dto) {

        int rstart = (page -1) * size;
        String redisKey = getBusiRedisKey(dto);
        // 无缓存
        if (!redisUtilz.hasKey(redisKey)){
            LambdaQueryWrapper<LikeRecord> lqw = new LambdaQueryWrapper<>();
            lqw.select(LikeRecord::getUserMid,LikeRecord::getModifiedDate);
            lqw.eq(LikeRecord::getLikeStatus,LikeConstant.LIKE_VALUE);
            lqw.eq(LikeRecord::getObjectId,dto.getObjectId());
            lqw.orderByDesc(LikeRecord::getModifiedDate);
            Page<LikeRecord> recordPage = this.page(new Page<>(1, 4000), lqw);
            // 重建缓存
            HashSet<ZSetOperations.TypedTuple<String>> set = new HashSet<>((int) recordPage.getTotal());
            for (LikeRecord x : recordPage.getRecords()) {
                ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<>(x.getUserMid(), (double) x.getModifiedDate().getTime());
                set.add(typedTuple);
            }
            if (CollectionUtil.isEmpty(recordPage.getRecords())){
                ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<>("", 0d);
                set.add(typedTuple);
            }
            redisUtilz.batchAddZset(redisKey,set);
            redisUtilz.expire(redisKey,60+ TimeUtilz.getRandomMinutes(), TimeUnit.MINUTES);
        }

        Set<String> ids = redisUtilz.reverseRangeByScore(redisKey, 1, 1,rstart,size);
        List<String> uids = null;
        if (ids.isEmpty() || ids.size() < size){
            // 去库中查数据
            LambdaQueryWrapper<LikeRecord> lqw = new LambdaQueryWrapper<>();
            lqw.select(LikeRecord::getUserMid);
            lqw.eq(LikeRecord::getLikeStatus,LikeConstant.LIKE_VALUE);
            lqw.eq(LikeRecord::getObjectId,dto.getObjectId());
            lqw.orderByDesc(LikeRecord::getModifiedDate);
            Page<LikeRecord> recordPage = page(new Page<>(page, size), lqw);
            if (!recordPage.getRecords().isEmpty()){
                uids = recordPage.getRecords().stream().map(LikeRecord::getUserMid).toList();
                // 更新redis缓存
                HashSet<ZSetOperations.TypedTuple<String>> set = new HashSet<>((int) recordPage.getTotal());
                for (LikeRecord x : recordPage.getRecords()) {
                    ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<>(x.getUserMid(), (double) x.getModifiedDate().getTime());
                    set.add(typedTuple);
                }
                redisUtilz.batchAddZset(redisKey,set);
            }
        }else {
            uids = ids.stream().toList();
        }
        if (CollectionUtil.isEmpty(uids)){
            return new ArrayList<>();
        }

        // 查用户信息
        Result<List<UserDto>> result = userClient.getUserInfoByIds(ids.toArray(String[]::new));
        if (result.isFail() || CollectionUtil.isEmpty(result.getData())){
            return new ArrayList<>();
        }
        return result.getData().stream().map(x -> {
            UserInfoVo vo = new UserInfoVo();
            vo.setAvatar(x.getAvatar());
            vo.setUsername(x.getUsername());
            vo.setDisplayName(x.getDisplayName());
            return vo;
        }).toList();
    }



    /**
     * 分页获取用户已点赞的实体id
     * @param page              当前页
     * @param size              分页大小
     */
    @Override
    public List<String> getLikedListByUser(int page, int size, LikeRecordDto dto) {

        int rstart = (page -1) * size;
        String redisKey = getUserRedisKey(dto);
        // 无缓存
        if (!redisUtilz.hasKey(redisKey)){
            LambdaQueryWrapper<LikeRecord> lqw = new LambdaQueryWrapper<>();
            lqw.select(LikeRecord::getObjectId,LikeRecord::getModifiedDate);
            lqw.eq(LikeRecord::getUserMid,dto.getUserMid());
            lqw.orderByDesc(LikeRecord::getModifiedDate);
            Page<LikeRecord> recordPage = this.page(new Page<>(1, 200), lqw);
            HashSet<ZSetOperations.TypedTuple<String>> set = new HashSet<>((int) recordPage.getTotal());
            for (LikeRecord x : recordPage.getRecords()) {
                ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<>(x.getObjectId(), (double) x.getModifiedDate().getTime());
                set.add(typedTuple);
            }
            redisUtilz.batchAddZset(redisKey,set);
        }

        Set<String> ids = redisUtilz.reverseRangeByScore(getUserRedisKey(dto), 1, 1,rstart,size);
        List<String> bids = null;
        if (ids.isEmpty() || ids.size() < size){
            // 去库中查数据
            LambdaQueryWrapper<LikeRecord> lqw = new LambdaQueryWrapper<>();
            lqw.select(LikeRecord::getObjectId);
            lqw.eq(LikeRecord::getLikeStatus,LikeConstant.LIKE_VALUE);
            lqw.eq(LikeRecord::getUserMid,dto.getUserMid());
            lqw.orderByDesc(LikeRecord::getModifiedDate);
            Page<LikeRecord> recordPage = page(new Page<>(page, size), lqw);
            if (!recordPage.getRecords().isEmpty()){
                bids = recordPage.getRecords().stream().map(LikeRecord::getObjectId).toList();
            }
        }else {
            bids = ids.stream().toList();
        }

        return CollectionUtil.isEmpty(bids)?new ArrayList<>():bids;
    }



    /**
     * 点赞或踩
     */
    @CacheEvict(value = "uLiked",  key = "#dto.userMid + ':' + #dto.objectType  + ':'+  #dto.objectId")
    @Override
    public LikeOperateEnum addLike(LikeRecordDto dto) {

        LikeOperateEnum opt = LikeOperateEnum.NONE;
        boolean result;
        // 操作为取消
        if (!Objects.equals(dto.getLikeStatus(), LikeConstant.DISLIKE_VALUE) && !Objects.equals(dto.getLikeStatus(), LikeConstant.LIKE_VALUE)){
            return opt;
        }

        String lockKey = LikeConstant.LOCK_LIKE_PREFIX + dto.getUserMid() + "_" + dto.getObjectId();
        RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock();
            //  查询是否已存在点赞信息
            LambdaQueryWrapper<LikeRecord> lqw = new LambdaQueryWrapper<>();
            lqw.select(LikeRecord::getId,LikeRecord::getLikeStatus);
            lqw.eq(LikeRecord::getObjectType,dto.getObjectType());
            lqw.eq(LikeRecord::getObjectId,dto.getObjectId());
            lqw.eq(LikeRecord::getUserMid,dto.getUserMid());
            LikeRecord record = getOne(lqw);

            if (record == null){
                // 新增
                LikeRecord saveRecord = BeanCopyUtils.copyBean(dto, LikeRecord.class);
                result = this.save(saveRecord);
            }else {
                if (dto.getLikeStatus().equals(record.getLikeStatus())){
                   return opt;
                }
                // 更新
                LambdaUpdateWrapper<LikeRecord> luw = new LambdaUpdateWrapper<>();
                luw.set(LikeRecord::getLikeStatus,dto.getLikeStatus());
                luw.eq(LikeRecord::getId,record.getId());
                result = this.update(luw);
            }

            LikeOperateEnum likeOperate = getLikeOperate(dto.getLikeStatus(), record == null ? null : record.getLikeStatus());
            if (result){
                // 异步任务
                likeAsyncJob.doAsyncJob(likeOperate,dto);
                // 删除缓存
                executorUtilz.delayedTask(()->{
                    redisUtilz.delete(getUserRedisKey(dto));
                    redisUtilz.delete(getBusiRedisKey(dto));
                });
            }
            return likeOperate;
        }finally {
            lock.unlock();
        }

    }


    /**
     * 取消点赞
     */
    @CacheEvict(value = "uLiked",  key = "#dto.userMid + ':' + #dto.objectType  + ':'+  #dto.objectId")
    @Override
    public LikeOperateEnum cancelLike(LikeRecordDto dto){

        LikeOperateEnum opt = LikeOperateEnum.FAIL;
        String lockKey = LikeConstant.LOCK_LIKE_PREFIX + dto.getUserMid() + "_" + dto.getObjectId();
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        try {
            // 查询点赞信息
            LambdaQueryWrapper<LikeRecord> lqw = new LambdaQueryWrapper<>();
            lqw.select(LikeRecord::getId,LikeRecord::getLikeStatus);
            lqw.eq(LikeRecord::getObjectType,dto.getObjectType());
            lqw.eq(LikeRecord::getObjectId,dto.getObjectId());
            lqw.eq(LikeRecord::getUserMid,dto.getUserMid());
            LikeRecord record = getOne(lqw);

            if (record == null || LikeConstant.NONE_VALUE.equals(record.getLikeStatus())){
                return opt;
            }
            // 更新
            LambdaUpdateWrapper<LikeRecord> luw = new LambdaUpdateWrapper<>();
            luw.set(LikeRecord::getLikeStatus,LikeConstant.NONE_VALUE);
            luw.eq(LikeRecord::getId,record.getId());
            boolean result = update(luw);

            if (result){
                opt = LikeConstant.LIKE_VALUE.equals(record.getLikeStatus())?LikeOperateEnum.LIKE_MINUS:LikeOperateEnum.DISLIKE_MINUS;
            }
            if (opt.isSuccess()){
                // 异步任务
                likeAsyncJob.doAsyncJob(opt,dto);
                // 删除缓存
                executorUtilz.delayedTask(()->{
                    redisUtilz.delete(getUserRedisKey(dto));
                    redisUtilz.delete(getBusiRedisKey(dto));
                });
            }
        }catch (Exception e){
            throw new BusinessException(e.getMessage(),e);
        }finally {
            lock.unlock();
        }
        return opt;
    }








    /**
     * 获取redis用户点赞key
     */
    private String getUserRedisKey(LikeRecordDto dto){
        return  LikeConstant.REDIS_USER_LIKE_LIST_PREFIX+dto.getUserMid()+NAME_SPACE+dto.getObjectType();
    }



    /**
     * 获取redis实体点赞key
     */
    private String getBusiRedisKey(LikeRecordDto dto){
        return  LikeConstant.REDIS_MESSAGE_LIKE_LIST_PREFIX+dto.getObjectType()+NAME_SPACE+dto.getObjectId();
    }


    /**
     * 获取当前的动作
     * @param currentStatus     当前操作
     * @param oldStatus         上次操作
     * @return                  要更新的总赞数量
     */
    private LikeOperateEnum getLikeOperate(Integer currentStatus,Integer oldStatus){

        // 点赞
        if (LikeConstant.LIKE_VALUE.equals(currentStatus)){
            return LikeConstant.DISLIKE_VALUE.equals(oldStatus)?LikeOperateEnum.DISLIKE_TO_LIKE:LikeOperateEnum.LIKE_PLUS;
        }
        // 踩
        if (LikeConstant.DISLIKE_VALUE.equals(currentStatus)){
            return LikeConstant.LIKE_VALUE.equals(oldStatus)?LikeOperateEnum.LIKE_TO_DISLIKE:LikeOperateEnum.DISLIKE_PLUS;
        }
        return LikeOperateEnum.NONE;

    }




}
